﻿#region Header

#region License

/*
Copyright (c) 2009, G.W. van der Vegt
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided
that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the
  following disclaimer.

* Redistributions in binary fForm must reproduce the above copyright notice, this list of conditions and
  the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of G.W. van der Vegt nor the names of its contributors may be
  used to endorse or promote products derived from this software without specific prior written
  permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#endregion License

#region Changelog

//----------   ---   -------------------------------------------------------------------------------
//Purpose:           End-User UI Translation Dialog
//By:                G.W. van der Vegt (wvd_vegt@knoware.nl)
//Url:               http://swiss.codeplex.com
//Depends:           Swiss.TranslationDlg
//License:           New BSD License
//----------   ---   -------------------------------------------------------------------------------
//dd-mm-yyyy - who - description
//----------   ---   -------------------------------------------------------------------------------
//01-01-2010 - veg - Created.
//                 - ListView SubItem Editing based on http://www.csharphelp.com/2006/10/c-editable-listview/.
//                 - Heavily modified above code to get rid of bugs and most fields.
//                 - Pass SubItem as Tag to TextBox when editing. Saves a Field.
//                 - Removed MouseDown EventHandler (and moved code into DoubleClick EventHandler.
//                 - Language ComboBox based on http://tipsntricksbd.blogspot.com/2007/12/combobox-is-one-of-most-common-gui.html
//03-01-2010   veg - Add Context Menu, Google Translate One/All (Suggest/GetWebPage code).
//                 - Add Event for adjusting the User-Interface (Pass Control and Before/After Size).
//                 - Saving Translation to ResX Files.
//                 - Add Method to set the TranslatingTo of the UI (and Translate it).
//                 - Set Properties based on path (support array indexes too, see ListView Columns)?
//                 - Tie ComboBox to reading and displaying resX files.
//                 - Before Returning the value convert '(n)' back to a '{n}' style string subsitution pattern.
//                 - Implemented rudimentary support for ListView ColumnsHeaders and ContextMenuStrips.
//                 - Implemented rudimentary support for Array Index for abovev two types.
//                 - Added Check number of substitution Patterns {n}.
//05-01-2010   veg - Switched to binary *.language.resource files.
//                 - Used code at http://blogs.msdn.com/feroze_daud/archive/2004/03/30/104440.aspx to translate the google page into the correct character set.
//                 - Fixed bug in Google url (UTF8 should have been UTF-8).
//                 - Added list of languages based on .NET Cultures.
//                 - Added label for Language.
//                 - Added retrieval of list of languages supported by Google Translate.
//                 - Based on above enable translation menu only when it should work.
//                 - Save/Load *.resource files at/from %appdata%.
//                 - Translations also expose the correct Culture & Region.
//                 - Only Translate Fields and Properties with a DescriptionAttribute.
//                 - Cleaned Code.
//                 - Added Xml Documentation.
//                 - Uploaded to swiss.codeplex.com
//                 - Set Accept/CancelButtons of the Form.
//                 - Now load Translations first (for names/groups), 
//                   then load en-US for column 1 and 
//                   finally the TranslateTo language for column 2.
//28-02-2010   veg - Added safety checks to CurrentRegionInfo (does not exists for neutral cultures).
//                 - Made CurrentCultureInfo and CurrentCultureRegion private show they are not translated.
//                   As they are inherited they show-up with GetProperties().
//                 - Added safety checks to InitialTranslation so null properties are not saved into the translation.
//----------   ---   -------------------------------------------------------------------------------
//TODO         veg - Add Preview Translation code (Just Replace Translation).
//             veg - Add Support for Resource Based Translation (Read/Only mode?).
//             veg + Upload to swiss.codePlex.com.
//
//             veg + Sort Strings inside Groups.
//----------   ---   -------------------------------------------------------------------------------
//NOTES
//----------   ---   -------------------------------------------------------------------------------

#endregion Changelog

#endregion Header

namespace Swiss
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Reflection;
    using System.Resources;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.Web;
    using System.Windows.Forms;

    /// <summary>
    /// Implements a Dialog ans Translation System that allows the 
    /// end-user to Translate the application including the default language.
    /// </summary>
    public partial class TranslationDlg : Form, IDisposable
    {
        #region Fields

        /// <summary>
        /// A Dictionary containing two letter / Ietf Language Codes and Language Names available at Google Translate.
        /// </summary>
        private Dictionary<String, String> GoogleTranslations = new Dictionary<String, String>();

        /// <summary>
        /// Used for editing the 2nd Column of the ListView.
        /// </summary>
        private System.Windows.Forms.TextBox textBox;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Constructor.
        /// a) Gets the Languages of Google Translate.
        /// b) enumerates the DotNet Framework for available Ietf Language Codes.
        /// </summary>
        public TranslationDlg()
        {
            InitializeComponent();

            textBox = new System.Windows.Forms.TextBox();
            textBox.Hide();

            SetGoogleLanguages();

            AddLocales();
        }

        /// <summary>
        /// Constructor.
        /// c) Sets the Language and updates the UI.
        /// d) Loads Translations.
        /// e) Gets Culture and Region Info.
        /// </summary>
        /// <param name="Translation">The Instance of Translations holding the Strings</param>
        /// <param name="TranslateTo">The Ietf Language Code to Translate to</param>
        public TranslationDlg(Translations Translation, String TranslateTo)
            : this()
        {
            this.Translation = Translation;

            SetLanguage(TranslateTo);

            //Thread.CurrentThread.CurrentUICulture = new CultureInfo(TranslatingTo);

            LoadTranslation(TranslateTo);

            UseLanguage = Language;

            Translation.CurrentCultureInfo = CultureInfo.GetCultureInfoByIetfLanguageTag(TranslateTo);
            if (!Translation.CurrentCultureInfo.IsNeutralCulture)
            {
                Translation.CurrentRegionInfo = new RegionInfo(Translation.CurrentCultureInfo.LCID);
            }
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Base Path for *.resource files, defaults to (%appdata%/ProductName).
        /// </summary>
        public static String ResourcePath
        {
            get
            {
                String AppDir = Path.GetDirectoryName(Application.ExecutablePath);

                String AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
                String AppTitle = Application.ProductName;

                if (String.IsNullOrEmpty(AppTitle))
                {
                    String[] split = AppDir.Split(Path.DirectorySeparatorChar);
                    AppTitle = Path.ChangeExtension(split[split.Length - 1], "");
                }

                return Path.Combine(AppData, AppTitle);
            }
        }

        /// <summary>
        /// Ietf Language Code Set by UseBtn.
        /// </summary>
        public static String UseLanguage
        {
            get;
            set;
        }

        /// <summary>
        /// Checks if the Current Language is available at Google Translate.
        /// </summary>
        private Boolean CanTranslate
        {
            get
            {
                String sl = Language;
                if (sl.Contains('-'))
                {
                    sl = sl.Remove(sl.IndexOf('-'));
                }

                return GoogleTranslations.ContainsKey(Language) || GoogleTranslations.ContainsKey(sl);
            }
        }

        /// <summary>
        /// The Current Ietf Language Code being shown/edited.
        /// </summary>
        private String Language
        {
            get;
            set;
        }

        /// <summary>
        /// Checks what Language Google Translate offers for a Ietf Language Code.
        /// </summary>
        private String SupportedLangauge
        {
            get
            {
                String sl = Language;
                if (sl.Contains('-'))
                {
                    sl = sl.Remove(sl.IndexOf('-'));
                }

                if (GoogleTranslations.ContainsKey(Language))
                {
                    return Language;
                }
                else if (GoogleTranslations.ContainsKey(sl))
                {
                    return sl;
                }
                else
                {
                    return "";
                }
            }
        }

        /// <summary>
        /// A placeholder for the current Translation Class
        /// </summary>
        private Translations Translation
        {
            get;
            set;
        }

        #endregion Properties

        #region Event Handlers

        private void TranslationDlg_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            //TODO Fails!

            //if (e.KeyCode == Keys.Escape)
            //{
            //    if (!textBox.Visible)
            //    {
            //        this.DialogResult = DialogResult.Cancel;
            //        this.Close();
            //    }
            //}
        }

        /// <summary>
        /// Setup TextBox control for editing the ListView's SubItems.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void TranslationDlg_Shown(object sender, EventArgs e)
        {
            //Parent TextBox Control..
            textBox.Parent = TranslationView;

            //Attach Event Handlers...
            textBox.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.textBox_KeyPress);
            textBox.LostFocus += new System.EventHandler(this.textBox_Leave);
        }

        /// <summary>
        /// Implements F2 editing of the Translation.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void TranslationView_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.F2)
            {
                ListViewItem li = ((ListView)sender).FocusedItem;

                //Bail-Out if no ListViewItem is clicked.
                if (li == null)
                {
                    return;
                }

                Int32 spos = 0;
                Int32 epos = ((ListView)sender).Columns[0].Width;

                for (Int32 i = 1; i < ((ListView)sender).Columns.Count; i++)
                {
                    spos = epos;
                    epos += ((ListView)sender).Columns[i].Width;
                }

                textBox.Size = new System.Drawing.Size(epos - spos, li.Bounds.Bottom - li.Bounds.Top);
                textBox.Location = new System.Drawing.Point(spos, li.Bounds.Y);

                //Pass SubItem as Tag to TextBox.
                textBox.Tag = li.SubItems[2];

                textBox.Text = li.SubItems[2].Text;

                textBox.Show();

                textBox.SelectAll();

                textBox.Focus();
                //TODO: How to get the active
            }
        }

        /// <summary>
        /// Edit when Double Clicked.
        /// 
        /// SEE http://www.csharphelp.com/2006/10/c-editable-listview/
        /// Allow editing of SubItems.
        /// Restrict it in this case to column 2 only!
        /// Modified code to get rid of most Fields and some bugs.
        /// Passing SubItem as Tag to TextBox.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void TranslationView_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            //Store a reference to the Item and Location clicked.
            Int32 mX = e.X;
            Int32 mY = e.Y;

            ListViewItem li = ((ListView)sender).GetItemAt(mX, mY);

            //Bail-Out if no ListViewItem is clicked.
            if (li == null)
            {
                return;
            }

            // Check the subitem clicked .
            Int32 nStart = mX;
            Int32 spos = 0;
            Int32 epos = ((ListView)sender).Columns[0].Width;
            Int32 subItemSelected = -1; //Assume No SubItem to be selected.

            for (Int32 i = 1; i < ((ListView)sender).Columns.Count; i++)
            {
                if (nStart > spos && nStart < epos)
                {
                    subItemSelected = i - 1;
                    break;
                }

                spos = epos;
                epos += ((ListView)sender).Columns[i].Width;
            }

            //Take last SubItem into account...
            if (subItemSelected == -1 && nStart > spos && nStart < epos)
            {
                subItemSelected = ((ListView)sender).Columns.Count - 1;
            }

            //Last chance to Bail-Out.
            if (subItemSelected == -1)
            {
                return;
            }

            if (subItemSelected == 2)
            {
                textBox.Size = new System.Drawing.Size(epos - spos, li.Bounds.Bottom - li.Bounds.Top);
                textBox.Location = new System.Drawing.Point(spos, li.Bounds.Y);

                //Pass SubItem as Tag to TextBox.
                textBox.Tag = li.SubItems[subItemSelected];

                textBox.Text = li.SubItems[subItemSelected].Text;

                textBox.Show();

                textBox.SelectAll();

                textBox.Focus();
            }
        }

        /// <summary>
        /// Sets the Currently Selected Ietf Language Code as the one to be used for the UI.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void UseBtn_Click(object sender, EventArgs e)
        {
            UseLanguage = ((LanguageComboBoxItem)languageSelector.SelectedItem).Value;

            UseBtn.Enabled = false;
        }

        /// <summary>
        /// Enables or Disables the Google Translation according to the availability of the Language.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
        {
            if (TranslationView.SelectedItems.Count != 0)
            {
                proposeAllToolStripMenuItem.Enabled = CanTranslate;
                proposeTranslationToolStripMenuItem.Enabled = CanTranslate;
            }
        }

        /// <summary>
        /// Updates the Translation to the selected one.
        /// 
        /// Note: It does not alter the UseLanguage Property!
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void languageSelector_SelectedIndexChanged(object sender, EventArgs e)
        {
            LoadTranslation(((LanguageComboBoxItem)languageSelector.SelectedItem).Value);

            UseBtn.Enabled = true;
        }

        /// <summary>
        /// TranslateAll MenuItem.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void proposeAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (new WaitCursor())
            {
                foreach (ListViewItem lvi in TranslationView.Items)
                {
                    if (lvi.SubItems.Count == 3 && String.IsNullOrEmpty(lvi.SubItems[2].Text))
                    {
                        TranslateItem(lvi);
                    }
                }
            }
        }

        /// <summary>
        /// Translate MenuItem.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void proposeTranslationToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (TranslationView.SelectedItems.Count != 0)
            {
                TranslateItem(TranslationView.SelectedItems[0]);
            }
        }

        /// <summary>
        /// Handle Enter and Escape for TextBox.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void textBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            switch ((Byte)e.KeyChar)
            {
                case 13:
                    {
                        if (textBox.Tag != null)
                        {
                            ((ListViewItem.ListViewSubItem)textBox.Tag).Text = textBox.Text;

                            CheckItems();

                            textBox.Tag = null;
                        }
                        textBox.Hide();

                        break;
                    }

                case 27:
                    {
                        textBox.Tag = null;
                        textBox.Hide();

                        break;
                    }
            }
        }

        /// <summary>
        /// Handle Leave (Focus Lost) as if Enter was pressed.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void textBox_Leave(object sender, EventArgs e)
        {
            if (textBox.Tag != null)
            {
                ((ListViewItem.ListViewSubItem)textBox.Tag).Text = textBox.Text;

                CheckItems();

                textBox.Tag = null;
            }
            textBox.Hide();
        }

        #endregion Event Handlers

        #region Methods

        /// <summary>
        /// This function is used to Show the TranslationDlg.
        /// </summary>
        /// <param name="Translations">The Instance of Translations holding the Strings</param>
        /// <param name="TranslateTo">An Ietf Language Code to Translate into</param>
        /// <returns></returns>
        public static Boolean Execute(Translations Translations, String TranslateTo)
        {
            using (TranslationDlg td = new TranslationDlg(Translations, TranslateTo))
            {
                if (td.ShowDialog() == DialogResult.OK)
                {
                    //Note Language can differ from UseLanguage.

                    td.SaveTranslation(td.Language);
                }
                else
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Generate an 'Initial' Translation by saving all fields and properties of the TranslationClass to a en-Us resource file.
        /// </summary>
        /// <param name="Translations">The Instance of Translations holding the Strings</param>
        public static void InitialTranslation(Translations Translations)
        {
            if (Translations != null)
            {
                String resource = Path.Combine(TranslationDlg.ResourcePath, String.Format("PicasaDownloader.{0}.resource", "en-US"));

                if (!File.Exists(resource))
                {
                    Debug.WriteLine("Creating initial translation: " + resource);

                    ResourceWriter writer = new ResourceWriter(resource);

                    //Fields
                    for (int i = 0; i < Translations.GetType().GetFields().Length; i++)
                    {
                        FieldInfo fi = Translations.GetType().GetFields()[i];
                        if (fi.GetValue(Translations) != null)
                        {
                            writer.AddResource(fi.Name, fi.GetValue(Translations).ToString());
                        }
                        else
                        {
                            Debug.WriteLine(String.Format("Field {0} is null", fi.Name));
                        }
                    }

                    //Properties
                    for (int i = 0; i < Translations.GetType().GetProperties().Length; i++)
                    {
                        PropertyInfo pi = Translations.GetType().GetProperties()[i];
                        if (pi.GetValue(Translations, null) != null)
                        {
                            writer.AddResource(pi.Name, pi.GetValue(Translations, null).ToString());
                        }
                        else
                        {
                            Debug.WriteLine(String.Format("Property {0} is null", pi.Name));
                        }
                    }

                    writer.Close();
                }
            }
        }

        /// <summary>
        /// Loads a resource file into the Translations Class by 
        /// translating all its Fields and Properties.
        /// 
        /// Note this Method can also be called from the Main application.
        /// </summary>
        /// <param name="TranslatingTo">The Ietf Language Code to load.</param>
        /// <param name="f">The Translation Class to Translate</param>
        /// <returns>true if there was a *.resource file for the requested Ietf Language Code</returns>
        public static Boolean Translate(string TranslatingTo, Translations f)
        {
            String resource = Path.Combine(ResourcePath, String.Format("PicasaDownloader.{0}.resource", TranslatingTo));
            if (File.Exists(resource))
            {
                ResourceReader reader = new ResourceReader(resource);

                IDictionaryEnumerator RrEn = reader.GetEnumerator();

                while (RrEn.MoveNext())
                {
                    String Key = (String)RrEn.Key;

                    f.TranslateMember(Key, (String)RrEn.Value);
                }
            }
            else
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// IDisposable Interface Implementation.
        /// </summary>
        void IDisposable.Dispose()
        {
            this.Dispose();
        }

        /// <summary>
        /// Decodes a WebResponse with the Correct Encoding.
        /// See: http://blogs.msdn.com/feroze_daud/archive/2004/03/30/104440.aspx
        /// </summary>
        /// <param name="w">The WebResponse to Decode</param>
        /// <returns>The Decoded result</returns>
        private static String DecodeData(WebResponse w)
        {
            //
            // first see if content length header has charset = calue
            //
            String charset = null;
            String ctype = w.Headers["content-type"];
            if (ctype != null)
            {
                int ind = ctype.IndexOf("charset=");
                if (ind != -1)
                {
                    charset = ctype.Substring(ind + 8);
                    Console.WriteLine("Detected Charset: " + charset);
                }
            }

            // save data to a memorystream
            MemoryStream rawdata = new MemoryStream();
            byte[] buffer = new byte[1024];
            Stream rs = w.GetResponseStream();
            int read = rs.Read(buffer, 0, buffer.Length);
            while (read > 0)
            {
                rawdata.Write(buffer, 0, read);
                read = rs.Read(buffer, 0, buffer.Length);
            }

            rs.Close();

            //
            // if ContentType is null, or did not contain charset, we search in body
            //
            if (charset == null)
            {
                MemoryStream ms = rawdata;
                ms.Seek(0, SeekOrigin.Begin);

                StreamReader srr = new StreamReader(ms, Encoding.ASCII);
                String meta = srr.ReadToEnd();

                if (meta != null)
                {
                    int start_ind = meta.IndexOf("charset=");
                    int end_ind = -1;
                    if (start_ind != -1)
                    {
                        end_ind = meta.IndexOf("\"", start_ind);
                        if (end_ind != -1)
                        {
                            int start = start_ind + 8;
                            charset = meta.Substring(start, end_ind - start + 1);
                            charset = charset.TrimEnd(new Char[] { '>', '"' });
                            Console.WriteLine("META: charset=" + charset);
                        }
                    }
                }
            }

            Encoding e = null;
            if (charset == null)
            {
                e = Encoding.ASCII; //default encoding
            }
            else
            {
                try
                {
                    e = Encoding.GetEncoding(charset);
                }
                catch (Exception ee)
                {
                    Console.WriteLine("Exception: GetEncoding: " + charset);
                    Console.WriteLine(ee.ToString());
                    e = Encoding.ASCII;
                }
            }

            rawdata.Seek(0, SeekOrigin.Begin);

            StreamReader sr = new StreamReader(rawdata, e);

            String s = sr.ReadToEnd();

            return s;
        }

        /// <summary>
        /// Enumerate the DotNet Framework for Ietf language Codes.
        /// </summary>
        private void AddLocales()
        {
            //Fill in all locales...
            languageSelector.Items.Clear();

            //create an array of CultureInfo to hold all the cultures found, these include the users local cluture, and all the
            //cultures installed with the .Net Framework
            CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures & ~CultureTypes.NeutralCultures);

            //loop through all the cultures found
            foreach (CultureInfo culture in cultures)
            {
                //if (culture.TwoLetterISOLanguageName.Contains('-'))
                //{
                //    Debug.WriteLine(culture.TwoLetterISOLanguageName);
                //}

                languageSelector.Items.Add(new LanguageComboBoxItem(culture.EnglishName, culture.IetfLanguageTag));
            }

            languageSelector.Sorted = true;
        }

        /// <summary>
        /// Helper to add a Key=Value Pair to the correct Group in the ListView.
        /// </summary>
        /// <param name="Key">The Key</param>
        /// <param name="Value">The Value</param>
        /// <param name="Group">Teh Group to add a ListViewItem to</param>
        /// <returns></returns>
        private Int32 AddTranslationItem(String Key, String Value, String Group)
        {
            ListViewItem lvi = TranslationView.Items.Add(Key, Key, -1);
            lvi.SubItems.Add(Value);
            lvi.SubItems.Add("");

            if (TranslationView.Groups[Group] == null)
            {
                TranslationView.Groups.Add(Group, Group);
            }
            lvi.Group = TranslationView.Groups[Group];

            return lvi.Index;
        }

        /// <summary>
        /// Check a ListViewItem for a matching number of String.Format subsitution patterns.
        /// </summary>
        /// <param name="lvi">The ListViewItem to Check</param>
        private void CheckItem(ListViewItem lvi)
        {
            //Check if number of string substitution patterns equal.
            if (Regex.Match(lvi.SubItems[2].Text, @"{(\d)}").Groups.Count !=
                Regex.Match(lvi.SubItems[1].Text, @"{(\d)}").Groups.Count)
            {
                lvi.BackColor = Color.Red;
            }
            else
            {
                lvi.BackColor = System.Drawing.SystemColors.Window;
            }
        }

        /// <summary>
        /// Checks all ListViewItems for matching number of String.Format subsitution patterns.
        /// </summary>
        private void CheckItems()
        {
            foreach (ListViewItem lvi in TranslationView.Items)
            {
                CheckItem(lvi);
            }
            TranslationView.Update();
        }

        /// <summary>
        /// Retrieve a Web Page.
        /// 
        /// Adapted from http://www.csharp-station.com/HowTo/HttpWebFetch.aspx
        /// </summary>
        /// <param name="url">The Url to retrieve</param>
        /// <returns>The content of the retrieved webpage or null if an arror has occurred</returns>
        private String GetWebPage(Uri url)
        {
            using (new WaitCursor())
            {
                try
                {
                    //Prepare the web page we will be asking for.
                    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

                    //Execute the request.
                    HttpWebResponse response = (HttpWebResponse)request.GetResponse();

                    return DecodeData(response);
                }
                catch (Exception e)
                {
                    Debug.WriteLine(e.Message);

                    return null;
                }
            }
        }

        /// <summary>
        /// Loads English and a Foreign Language.
        /// </summary>
        /// <param name="TranslatingTo">The Ietf Language Code to Translate to</param>
        private void LoadTranslation(String TranslatingTo)
        {
            String resource = String.Empty;

            Language = TranslatingTo;

            TranslationView.Items.Clear();
            TranslationView.Groups.Clear();

            //Load Class for Grouping Attributes & Field/Property Names.

            //Messages
            for (int i = 0; i < Translation.GetType().GetFields().Length; i++)
            {
                FieldInfo fi = Translation.GetType().GetFields()[i];

                String Group = "None";
                DescriptionAttribute[] attributes = (DescriptionAttribute[])
                    fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attributes.Length > 0)
                {
                    Group = attributes[0].Description;
                }
                else
                {
                    //Skip Fields without Attribute.
                    continue;
                }

                String Key = fi.Name;
                String Value = fi.GetValue(Translation).ToString();

                AddTranslationItem(Key, Value, Group);
            }

            //Properties
            for (int i = 0; i < Translation.GetType().GetProperties().Length; i++)
            {
                PropertyInfo pi = Translation.GetType().GetProperties()[i];

                String Group = "None";
                DescriptionAttribute[] attributes = (DescriptionAttribute[])
                    pi.GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attributes.Length > 0)
                {
                    Group = attributes[0].Description;
                }
                else
                {
                    //Skip Fields without Attribute.
                    continue;
                }

                String Key = pi.Name;
                String Value = pi.GetValue(Translation, null).ToString();

                AddTranslationItem(Key, Value, Group);
            }

            //Load en-US orginal

            resource = Path.Combine(ResourcePath, String.Format("PicasaDownloader.{0}.resource", "en-US"));

            if (File.Exists(resource))
            {
                Debug.WriteLine("Loading translation: " + resource);

                ResourceReader reader = new ResourceReader(resource);

                IDictionaryEnumerator RrEn = reader.GetEnumerator();
                while (RrEn.MoveNext())
                {
                    String Key = (String)RrEn.Key;
                    String Value = (String)RrEn.Value;
                    String Group = "None";

                    Int32 ndx = TranslationView.Items.IndexOfKey(Key);

                    if (ndx == -1)
                    {
                        ndx = AddTranslationItem(Key, "**Error**", Group);
                    }

                    TranslationView.Items[ndx].SubItems[1].Text = Value;
                }

                reader.Close();
            }
            else
            {
                Debug.WriteLine("Missing {translation: " + resource);
            }

            TranslationView.Update();

            //Load Translated version

            resource = Path.Combine(ResourcePath, String.Format("PicasaDownloader.{0}.resource", Language));

            if (File.Exists(resource))
            {
                Debug.WriteLine("Loading translation: " + resource);

                ResourceReader reader = new ResourceReader(resource);

                IDictionaryEnumerator RrEn = reader.GetEnumerator();
                while (RrEn.MoveNext())
                {
                    String Key = (String)RrEn.Key;
                    String Value = (String)RrEn.Value;
                    String Group = "None";

                    Int32 ndx = TranslationView.Items.IndexOfKey(Key);

                    if (ndx == -1)
                    {
                        ndx = AddTranslationItem(Key, "**Error**", Group);
                    }

                    TranslationView.Items[ndx].SubItems[2].Text = Value;
                }

                reader.Close();
            }
            else
            {
                Debug.WriteLine("Missing translation: " + resource);
            }

            TranslationView.Columns[0].Width = -2;

            CheckItems();
        }

        /// <summary>
        /// Saves a Foreign Language.
        /// </summary>
        /// <param name="TranslateTo">OnTranslated Language</param>
        private void SaveTranslation(String TranslateTo)
        {
            Language = TranslateTo;

            String resource = Path.Combine(ResourcePath, String.Format("PicasaDownloader.{0}.resource", TranslateTo));

            Debug.WriteLine("Saving translation: " + resource);

            ResourceWriter rw = new ResourceWriter(resource);
            foreach (ListViewItem lvi in TranslationView.Items)
            {
                rw.AddResource(lvi.Text, lvi.SubItems[2].Text);
            }
            rw.Close();
        }

        /// <summary>
        /// Fetches a page from Google Translate.
        /// Extracts all 2/4 letter Language Codes and Names supported.
        /// Stores them into the GoogleTranslations Dictionary.
        /// </summary>
        private void SetGoogleLanguages()
        {
            GoogleTranslations.Clear();

            //{0} UI Language.
            //{1} Text to translate.
            //{2} From|To LanguageID.
            String url = String.Format("http://www.google.com/translate_t?hl={0}", "en");

            String Result = GetWebPage(new Uri(url));

            if (!String.IsNullOrEmpty(Result))
            {
                Regex rg = new Regex("<option[ \\t]*value=\"(?<lang>..)(?<langsuffix>-..)?\">(?<name>[\\w]*)([ ])*(?<namesuffix>\\((\\w)*\\)?)?</option>");

                Match m = rg.Match(Result);
                while (m.Success)
                {
                    String lang = m.Groups["lang"].Value + m.Groups["langsuffix"].Value;
                    String suffix = (String.IsNullOrEmpty(m.Groups["namesuffix"].Value) ? "" : " " + m.Groups["namesuffix"].Value);
                    String name = m.Groups["name"].Value + suffix;

                    Debug.WriteLine(lang + " = " + name);

                    if (!GoogleTranslations.ContainsKey(lang))
                    {
                        GoogleTranslations.Add(m.Groups["lang"].Value + m.Groups["langsuffix"].Value, m.Groups["name"].Value + suffix);
                    }

                    m = m.NextMatch();
                }
            }
        }

        /// <summary>
        /// Sets the Language Property and updates the UI accordingly.
        /// 
        /// Note: It does not load the Translations.
        /// </summary>
        /// <param name="TranslateTo">An Ietf Language Code to Translate into</param>
        private void SetLanguage(String TranslateTo)
        {
            Language = TranslateTo;

            label1.Text = "none";

            //Loop trough languageSelector to find the requested language.
            foreach (LanguageComboBoxItem cbi in languageSelector.Items)
            {
                if (cbi.Value.Equals(Language))
                {
                    languageSelector.SelectedItem = cbi;

                    //Update Info Label
                    if (CanTranslate)
                    {
                        label1.Text = cbi.Value + " | Google Translate available for: " + SupportedLangauge;
                    }
                    else
                    {
                        label1.Text = cbi.Value + " | Google Translate unavailable";
                    }

                    break;
                }
            }
        }

        /// <summary>
        /// Performs a Translation by calling Google Translate
        /// After retrieval and decoding of the WebPage the translated string is extracted.
        /// </summary>
        /// <param name="TranslatingTo">The 2/4 letter language code to use.</param>
        /// <param name="value">The String to translate</param>
        /// <returns></returns>
        private String Suggest(String TranslatingTo, String value)
        {
            //Shorten Language but not for two Chinese dialect.
            String sl = TranslatingTo;
            if (sl.Contains('-') && !sl.StartsWith("zh"))
            {
                sl = sl.Remove(sl.IndexOf('-'));
            }

            //{0} UI Language.
            //{1} Text to translate.
            //{2} From|To LanguageID.
            String url = String.Format("http://www.google.com/translate_t?hl={0}&ie=UTF-8&text={1}&langpair={2}", "en", value, String.Format("en|{0}", sl));

            Debug.WriteLine("Google Translate Url: " + url);

            String Result = GetWebPage(new Uri(url));

            if (!String.IsNullOrEmpty(Result))
            {
                Regex rg = new Regex(@"<span id=result_box(.*)><span(.*)>(.*)</span></span>");
                GroupCollection gc = rg.Match(Result).Groups;

                if (gc.Count == 4) //3 groups & total match/
                {
                    //Before Returning the value convert '(n)' back to a '{n}' style string subsitution pattern.
                    String Decoded = HttpUtility.HtmlDecode(gc[gc.Count - 1].Value);

                    Match m = Regex.Match(Decoded, @"\((\d)\)");
                    while (m.Success)
                    {
                        Decoded = Decoded.Replace(String.Format("({0})", m.Groups[1].Value), "{" + m.Groups[1].Value + "}");

                        m = m.NextMatch();
                    }

                    return Decoded;
                }
            }

            return value;
        }

        /// <summary>
        /// Translates a single Item by calling Google Translate.
        /// </summary>
        /// <param name="lvi">The ListViewItem to Translate</param>
        private void TranslateItem(ListViewItem lvi)
        {
            if (lvi != null && lvi.SubItems.Count == 3)
            {
                lvi.SubItems[2].Text = Suggest(Language, lvi.SubItems[1].Text);

                TranslationView.Columns[2].Width = -1;

                CheckItem(lvi);

                TranslationView.Update();
            }
        }

        #endregion Methods

        #region Nested Types

        /// <summary>
        /// This is the base class for translation for the application. 
        /// 
        /// In the application derive a subclass of this.
        /// 
        /// It can consist of:
        ///   a) Fields
        ///   b) Properties
        /// 
        /// Both types of members must be of the type String and 
        /// have a DescriptionAttribute for grouping in the TranslationDlg.
        /// 
        /// Because properties have a Setter they can be used to translate Controls and adjust the user-interface by coding.
        /// For this a reference must be stored to the Form/Forms being transated so the compiler is able to check references.
        /// 
        /// For adjusting the user-interface, the Translations class implements a SetText that returns 
        /// the size and position adjustments the DotNet FrameWork made by setting the Text property.
        /// 
        /// Note: Reflection cannot be used for this as there are unnamed controls (ListViewHeaders) and 
        /// objects that are not easily discovered by reflection like ToolStripItems.
        /// 
        /// Usage: 
        /// 
        /// 1) Generating Initial Translation:
        /// 
        ///    TranslationDlg.InitialTranslation(CustomTranslation);
        ///
        /// 2) Translate:
        /// 
        ///    if (!TranslationDlg.Translate(Language, CustomTranslation))
        ///    {
        ///      //failure to translate
        ///    }
        ///    
        /// 3) Show and Translate:
        ///
        ///    if (TranslationDlg.Execute(CustomTranslation, Language))
        ///    {
        ///       TranslationDlg.Translate(TranslationDlg.UseLanguage, CustomTranslation);
        ///    }
        ///    
        /// 4) CustomTranslation example:
        /// 
        ///    public class CustomTranslations : TranslationDlg.Translations
        ///    {
        ///      [DescriptionAttribute("Tooltip")]
        ///      public String sAlbumId = "Album Id:";
        ///      
        ///      private Form1 fForm;
        ///      public CustomTranslations(Form1 form) : base()
        ///      {
        ///        this.fForm = form;
        ///      }
        ///
        ///      [DescriptionAttribute("Buttons")]
        ///      public String cListBtn
        ///      {
        ///        get { return fForm.ListBtn.Text; }
        ///        set
        ///        {
        ///          Rectangle deltaBounds = SetText(fForm.ListBtn, value);
        ///                  
        ///          fForm.textBox1.Width = fForm.textBox1.Width - deltaBounds.Width;
        ///        }
        ///      }
        ///
        ///      [DescriptionAttribute("Columns")]
        ///      public String cNumberOfPhotosColumn
        ///      {
        ///        get { return fForm.NumberOfPhotosColumn.Text; }
        ///        set { fForm.NumberOfPhotosColumn.Text = value; }
        ///      }
        ///   }
        ///   
        ///   In above code: 
        ///   - sAlbumId is a String that can be used to generate messages.
        ///   - The constructor sets an internal var to Form1 (so the compiler can do its job).
        ///   - cListBtn is a translation with Width adjustment of a neighbouring textBox.
        ///   - cNumberOfPhotosColumn is the translation of a ListViewHeader
        /// </summary>
        public class Translations
        {
            #region Constructors

            /// <summary>
            /// Just a Dummy Constructor.
            /// </summary>
            public Translations()
            {
                //Nothing.
            }

            #endregion Constructors

            #region Properties

            /// <summary>
            /// Holds the CultureInfo of the Ietf Language Code.
            /// </summary>
            internal CultureInfo CurrentCultureInfo
            {
                set;
                get;
            }

            /// <summary>
            /// Holds the Region of the Ietf Language Code.
            /// 
            /// Warning, for neutral cultures there is no Region.
            /// </summary>
            internal RegionInfo CurrentRegionInfo
            {
                set;
                get;
            }

            #endregion Properties

            #region Methods

            /// <summary>
            /// Sets the Text of a Control and returns the differnce in Size and Position.
            /// </summary>
            /// <param name="c">The Control to Translate</param>
            /// <param name="text">The Translated Text</param>
            /// <returns>A Rectangle containing the difference in Size and Position</returns>
            public Rectangle SetText(Control c, String text)
            {
                Rectangle before = (Rectangle)(c.GetType().GetProperty("Bounds").GetValue(c, null));

                c.GetType().GetProperty("Text").SetValue(c, text, null);

                Rectangle after = (Rectangle)(c.GetType().GetProperty("Bounds").GetValue(c, null));

                Rectangle delta = new Rectangle(
                    after.Left - before.Left,
                    after.Top - before.Top,
                    after.Width - before.Width,
                    after.Height - before.Height);

                return delta;
            }

            /// <summary>
            /// This function does the actual Translation. 
            /// 
            /// It recurces until two Parts are left.
            /// 
            /// These two then are consisered to be the name of the Control and the name of the property or field to be translated.
            /// </summary>
            /// <param name="Key">The complete path of the property or field being translated</param>
            /// <param name="Value">The Value to set</param>
            internal void TranslateMember(String Key, String Value)
            {
                //See if we have a Property or a Field...

                PropertyInfo pi = this.GetType().GetProperty(Key);
                if (pi != null)
                {
                    pi.SetValue(this, Value, null);
                    return;
                }
                else
                {

                    //String Fields are just Translated.

                    FieldInfo fi = this.GetType().GetField(Key);
                    if (fi != null)
                    {
                        fi.SetValue(this, Value);
                    }
                    else
                    {
                        //Error: Not a Property or Field.

                        Debug.WriteLine(String.Format("Translation property or field not found: {0}", Key));
                    }
                }
            }

            #endregion Methods
        }

        /// <summary>
        /// Implements a Key=Value pair for usage in a ComboBox.
        /// </summary>
        private class LanguageComboBoxItem
        {
            #region Fields

            /// <summary>
            /// The Name of the Language.
            /// </summary>
            public String Name;

            /// <summary>
            /// The value (Ietf Language Code).
            /// </summary>
            public String Value;

            #endregion Fields

            #region Constructors

            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="Name">The Name of the Language</param>
            /// <param name="Value">The Ietf Language Code</param>
            public LanguageComboBoxItem(String Name, String Value)
            {
                this.Name = Name;
                this.Value = Value;
            }

            #endregion Constructors

            #region Methods

            /// <summary>
            /// Override ToString() function so it only displays the Name.
            /// </summary>
            /// <returns>The Name of the LanguageComboBoxItem</returns>
            public override String ToString()
            {
                return this.Name;
            }

            #endregion Methods
        }

        #endregion Nested Types
    }
}